/*
 * Wazuh Vulnerability scanner - Scan Orchestrator
 * Copyright (C) 2015, Wazuh Inc.
 * March 11, 2024.
 *
 * This program is free software; you can redistribute it
 * and/or modify it under the terms of the GNU General Public
 * License (version 2) as published by the FSF - Free Software
 * Foundation.
 */

#ifndef _EVENT_DETAILS_BUILDER_HPP
#define _EVENT_DETAILS_BUILDER_HPP

#include "chainOfResponsability.hpp"
#include "databaseFeedManager.hpp"
#include "numericHelper.h"
#include "scanContext.hpp"
#include "timeHelper.h"

constexpr auto WAZUH_SCHEMA_VERSION = "1.0.0";

/**
 * @brief TEventDetailsBuilder class.
 * This class is responsible for building the event details for the vulnerability event.
 * It receives the scan context and the database feed manager and returns the scan context with the event details.
 * Its neccessary to have the context->m_elements populated with the cve and the operation.
 * The operation can be "DELETED" or "INSERTED".
 * Its also necessary to have the context->m_matchConditions populated with the cve and the condition.
 * The condition can be "LessThanOrEqual", "LessThan", "DefaultStatus" or "Equal".
 *
 * @tparam TDatabaseFeedManager database feed manager type.
 * @tparam TScanContext scan context type.
 */
template<typename TDatabaseFeedManager = DatabaseFeedManager, typename TScanContext = ScanContext>
class TEventDetailsBuilder final : public AbstractHandler<std::shared_ptr<TScanContext>>
{
private:
    std::shared_ptr<TDatabaseFeedManager> m_databaseFeedManager;

    /**
     * @brief Populate a JSON field with a value if the value is non-empty (for strings) or always (for other types).
     *
     * This function takes a JSON object, a JSON pointer specifying the field to populate, and a value to populate the
     * field with. If the value is a string or a string view and is not empty, it will populate the field in the JSON
     * object. For other types, the field will be populated regardless of the value.
     *
     * @tparam T       The type of the value to populate the field with.
     * @param json     The JSON object to populate.
     * @param key      The JSON pointer specifying the field to populate.
     * @param value    The value to populate the field with.
     */
    template<typename T>
    void populateField(nlohmann::json& json, const nlohmann::json::json_pointer& key, T&& value)
    {
        if constexpr (std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>, std::string_view> ||
                      std::is_same_v<T, std::string>)
        {
            const auto valueTrimmed = Utils::trim(value.data());

            if (!valueTrimmed.empty())
            {
                json[key] = value;
            }
        }
        else
        {
            json[key] = value;
        }
    }

public:
    // LCOV_EXCL_START
    /**
     * @brief EventDetailsBuilder constructor.
     *
     * @param databaseFeedManager Database feed manager.
     */
    explicit TEventDetailsBuilder(std::shared_ptr<TDatabaseFeedManager>& databaseFeedManager)
        : m_databaseFeedManager(databaseFeedManager)
    {
    }

    /**
     * @brief Class destructor.
     *
     */
    ~TEventDetailsBuilder() = default;
    // LCOV_EXCL_STOP

    /**
     * @brief Handles request and passes control to the next step of the chain.
     *
     * @param data Scan context.
     * @return std::shared_ptr<ScanContext> Abstract handler.
     */
    std::shared_ptr<TScanContext> handleRequest(std::shared_ptr<TScanContext> data) override
    {
        nlohmann::json package;

        if (data->affectedComponentType() == AffectedComponentType::Package)
        {
            populateField(package, "/architecture"_json_pointer, data->packageArchitecture());
            populateField(package, "/description"_json_pointer, data->packageDescription());

            if (!data->packageInstallTime().empty())
            {
                const auto installTime {Utils::rawTimestampToISO8601(data->packageInstallTime().data())};
                if (!installTime.empty())
                {
                    package["installed"] = installTime;
                }
            }
            populateField(package, "/name"_json_pointer, data->packageName());
            populateField(package, "/path"_json_pointer, data->packageLocation());
            populateField(package, "/size"_json_pointer, data->packageSize());
            populateField(package, "/type"_json_pointer, data->packageFormat());
            populateField(package, "/version"_json_pointer, data->packageVersion());
        }

        nlohmann::json agent;
        populateField(agent, "/ephemeral_id"_json_pointer, data->agentNodeName());
        populateField(agent, "/id"_json_pointer, data->agentId());
        populateField(agent, "/name"_json_pointer, data->agentName());
        populateField(agent, "/type"_json_pointer, "wazuh");
        populateField(agent, "/version"_json_pointer, data->agentVersion());

        nlohmann::json os;
        std::string fullName;
        fullName.append(data->osName().data());
        fullName.append(" ");
        fullName.append(data->osPlatform().compare("darwin") == 0 ? data->osCodeName().data()
                                                                  : data->osVersion().data());

        std::string version = data->osMajorVersion().data();
        if (!data->osMinorVersion().empty())
        {
            version += ".";
            version += data->osMinorVersion();
        }
        if (!data->osPatch().empty())
        {
            version += ".";
            version += data->osPatch();
        }
        if (!data->osBuild().empty())
        {
            version += ".";
            version += data->osBuild();
        }

        populateField(os, "/full"_json_pointer, fullName);
        populateField(os, "/kernel"_json_pointer, data->osKernelRelease());
        populateField(os, "/name"_json_pointer, data->osName());
        populateField(os, "/platform"_json_pointer, Utils::toLowerCase(data->osPlatform().data()));
        populateField(
            os,
            "/type"_json_pointer,
            Utils::toLowerCase(data->osPlatform().compare("darwin") == 0 ? "macos" : data->osPlatform().data()));
        populateField(os, "/version"_json_pointer, version);

        for (auto& [cve, json] : data->m_elements)
        {
            FlatbufferDataPair<VulnerabilityDescription> returnData;
            m_databaseFeedManager->getVulnerabiltyDescriptiveInformation(cve, returnData);
            if (returnData.data)
            {
                auto ecsData = nlohmann::json::object();

                // ECS agent fields.
                ecsData["agent"] = agent;

                // ECS package fields.
                if (data->affectedComponentType() == AffectedComponentType::Package)
                {
                    ecsData["package"] = package;
                }

                // ECS os fields.
                ecsData["host"]["os"] = os;

                // ECS vulnerability fields.
                ecsData["vulnerability"]["category"] = "Packages";
                ecsData["vulnerability"]["classification"] = returnData.data->classification()->str();
                ecsData["vulnerability"]["description"] = returnData.data->description()->str();
                ecsData["vulnerability"]["detected_at"] = Utils::getCurrentISO8601();
                ecsData["vulnerability"]["enumeration"] = "CVE";
                ecsData["vulnerability"]["id"] = cve;
                ecsData["vulnerability"]["published_at"] = returnData.data->datePublished()->str();
                ecsData["vulnerability"]["reference"] = returnData.data->reference()->str();
                ecsData["vulnerability"]["scanner"]["vendor"] = "Wazuh";
                ecsData["vulnerability"]["score"]["base"] = Utils::floatToDoubleRound(returnData.data->scoreBase(), 2);
                ecsData["vulnerability"]["score"]["version"] = returnData.data->scoreVersion()->str();
                ecsData["vulnerability"]["severity"] = Utils::toSentenceCase(returnData.data->severity()->str());

                // ECS wazuh fields.
                auto vulnerabilityDetection = PolicyManager::instance().getVulnerabilityDetection();
                ecsData["wazuh"]["cluster"]["name"] =
                    vulnerabilityDetection.contains("clusterName") ? vulnerabilityDetection.at("clusterName") : "";
                ecsData["wazuh"]["manager"]["name"] = data->managerName();
                ecsData["wazuh"]["schema"]["version"] = WAZUH_SCHEMA_VERSION;

                json["data"] = std::move(ecsData);
            }
        }

        return AbstractHandler<std::shared_ptr<TScanContext>>::handleRequest(std::move(data));
    }
};

using EventDetailsBuilder = TEventDetailsBuilder<>;

#endif // _EVENT_DETAILS_BUILDER_HPP
